Skip to content

KMP migration M1: multiplatform core + Compose UI, Android target - #15

Merged
michelutke merged 61 commits into
mainfrom
feature/kmp-migration-m1
Jul 23, 2026
Merged

KMP migration M1: multiplatform core + Compose UI, Android target#15
michelutke merged 61 commits into
mainfrom
feature/kmp-migration-m1

Conversation

@michelutke

@michelutke michelutke commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

What

Full rewrite of the Updraft SDK as Kotlin Multiplatform (milestone M1: Android target only, iOS follows in #14). One artifact becomes three:

Module Coordinates Content
:updraft-core com.appswithlove.updraft:updraft-core:2.0.0 Logic only: Ktor client, coroutines/Flow, Updraft.start(UpdraftSettings), Updraft.events: SharedFlow<UpdraftEvent>, update/feedback interactors, expect/actual platform seams (shake, screenshot, key-value store, app info, foreground observer, URL opening).
:updraft-ui-compose com.appswithlove.updraft:updraft-ui-compose:2.0.0 Compose Multiplatform feedback UI: FeedbackScreen (annotate + form), DrawingCanvas (FreeDrawView replacement with undo/redo), dialogs, UpdraftEventHost. Strings en/de via compose-resources.
:updraft-sdk unchanged coordinates Thin Android wrapper: two host activities + androidx.startup auto-wire — drop-in convenience artifact, no manual setup.

The entire legacy stack (~3500 lines of Views/Fragments/FreeDrawView/Retrofit/RxJava2) is deleted.

Why

  • One codebase for Android and iOS features (auto-update, shake-to-feedback, annotation) instead of maintaining updraft-sdk-android and updraft-sdk-ios separately with diverging feature sets.
  • Retire the legacy stack: RxJava 2 and the Views/Fragments UI were the main obstacles to sharing anything with iOS.
  • Consumers get tiered integration: all-in-one Android artifact, logic-only core for apps with their own UI, or core + CMP UI hosted inside the app's own Compose tree.

How

  • HTTP: Retrofit/OkHttp → Ktor (content-negotiation + kotlinx.serialization, upload progress via onUploadcallbackFlow<Double>, expectSuccess so HTTP errors fail the upload flow).
  • Async: RxJava 2 → coroutines; SDK events are a SharedFlow<UpdraftEvent> that the wrapper (or the host app) collects.
  • UI: XML/Fragments → Compose Multiplatform in commonMain; drawing is a ~100-line custom controller (path list + undo/redo stacks) instead of the FreeDrawView port. Annotation strokes are recorded in canvas space and re-projected onto the full-resolution screenshot at send time so uploads align.
  • Platform seams are expect/actual functions, kept deliberately small so the iOS milestone only adds actuals.
  • Breaking API: 2.0.0 — SettingsUpdraftSettings, Updraft.initialize(context, settings) + getInstance()?.start()Updraft.start(settings). Migration table in the README.
  • Full build + unit tests green; each task went through spec/quality review loops. Release checklist (publish workflow, manual device verification) tracked in docs/kmp-migration-m1-status.md — device verification happened on the M2 branch and found real issues there, see KMP migration M2: iOS support, device verification round, CMP rebrand #14.

Decision: Compose UI also for Views-based Android apps

updraft-sdk ships the Compose feedback UI to all Android apps, including classic Views apps — there will be no separate updraft-ui-views artifact.

  • Why: a single UI codebase across all four consumer types (Android Views, Android Compose, Compose Multiplatform, iOS) is the core value of this migration; a parallel Views implementation would reintroduce exactly the duplication we just deleted.
  • Cost: ~2 MB APK size for apps that don't ship Compose yet. Accepted — R8 strips unused Compose code, and most actively developed apps already include Compose or are heading there.
  • Isolation: the SDK hosts Compose inside its own activities (UpdraftFeedbackActivity, UpdraftOverlayActivity), so nothing Compose-related touches the host app's view hierarchy or theme.
  • Escape hatch: size-sensitive Views apps can depend on updraft-core only (logic, no UI, no Compose) and bring their own feedback screen — documented in the README ("bring your own UI").

Closes #12 — the legacy Views feedback flow crashed on Jetpack Compose UIs; the rewritten Compose-based flow replaces it entirely.

…ng URL

The server field is device_uudid (as sent by the iOS SDK), not
device_uuid, so the UUID never showed up on the dashboard. Navigation
stack was never implemented on Android; it now reports the host app's
activity stack (Android) or view controller chain (iOS), excluding
Updraft's own screens. BASE_URL_STAGING pointed at u2.mqd.me which no
longer resolves; baseUrl stays overridable for self-hosted instances.
Navigation stack is captured when feedback is triggered, before the
Updraft UI covers the app. Apps using single-activity navigation
(Compose Navigation, Navigation Component) can plug their library in
via Updraft.navigationStackProvider; the platform default reports the
activity stack (Android) or view controller chain (iOS). Also fixes
the package filter that swallowed host activities whose package merely
shares the com.appswithlove.updraft prefix, like the sample app.
The legacy iOS SDK never used shake detection; its feedback trigger was
UIApplicationUserDidTakeScreenshotNotification. The accelerometer-based
shake also cannot work on the simulator (no accelerometer), so shake
appeared broken there. Both triggers are now active on iOS: screenshot
(all environments, legacy parity) and shake (physical devices).
Shake reaches iOS apps as a responder-chain motion event, not as
accelerometer data, so the CMMotionManager detector never fired on the
simulator and required nothing in the responder chain to consume the
event on devices. The SDK now swizzles UIWindow.motionEnded:withEvent:
at start, calling through to the original implementation. The IMP is a
staticCFunction rather than a Kotlin lambda: block bridging would
marshal the NSInteger motion argument as an object and crash.
The repo stays and becomes the single home for Android + iOS: root
project renamed to updraft-sdk, POM name/URLs point at the new repo
slug, README repositions the SDK as Kotlin/Compose Multiplatform and
notes that updraft-sdk-ios is superseded. GitHub-side rename and
archiving updraft-sdk-ios remain manual release-time steps.
@michelutke michelutke self-assigned this Jul 22, 2026
@michelutke michelutke changed the title Feature/kmp migration m1 KMP migration M1: multiplatform core + Compose UI, Android target Jul 22, 2026
… filter

Superseded runs on the same ref are cancelled, ~/.konan is cached (the
Kotlin/Native toolchain was re-provisioned on every macOS run before),
gradle/actions/setup-gradle replaces the basic setup-java cache and
validates the wrapper jar, markdown/docs pushes no longer trigger
builds, timeouts bound hung runs, and the redundant pull_request
trigger and chmod steps are removed (exec bit is committed).
…rsions

Publishing previously ran no tests: a bad push to production would ship
broken artifacts that Maven Central cannot replace. The job now runs
the full build plus iOS simulator tests first, fails fast if the
version is already tagged, publishes under a 'maven-central' GitHub
environment (protection rules can be added in settings), and uses the
maintained softprops/action-gh-release instead of the archived
actions/create-release. JDK unified to 17 to match CI.
The SDK is built with Kotlin 2.2 / CMP 1.9: Kotlin Android consumers
need >= 2.1 (metadata n+1 rule), KMP/CMP consumers >= 2.2, Java-only
and XCFramework consumers are unaffected. The README lists the exact
compile-time errors older projects will see and the fix.
The legacy SDK fetched strings from Loco into Android res/; Compose
Multiplatform resources use the same strings.xml format, so updateLoco
now writes into updraft-ui-compose commonMain composeResources and both
platforms share the result. The API key moves from the build file
(exposed in git history — rotate it) to local.properties.
@michelutke
michelutke requested a review from yannickpulver July 22, 2026 14:00
@michelutke
michelutke requested review from vladislavfrolov and removed request for yannickpulver July 23, 2026 06:51
michelutke and others added 4 commits July 23, 2026 09:49
Loco is the source of truth again: asset IDs renamed to underscores
(compose-resources rejects dots), the ten strings missing from Loco
added with German translations, and the draw-hint copy fixed there.
This file state is the updateLoco output: alphabetical order and the
removal of two unused legacy plurals (relative-age formatting was never
implemented in 2.0).
Restores the legacy 'Released 3 weeks ago. You're currently on version
X.' message using kotlin.time (no new dependency). Buckets: just now
under an hour, then hours/days/weeks/months/years plurals. The plurals
live in plurals.xml so updateLoco, which only rewrites strings.xml,
cannot clobber them. Falls back to the previous message when create_at
is missing or unparseable.
KMP migration M2: iOS support, device verification round, CMP rebrand
@michelutke
michelutke merged commit b9d20f5 into main Jul 23, 2026
2 checks passed
@michelutke
michelutke deleted the feature/kmp-migration-m1 branch July 23, 2026 13:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Updraft SDK — shake-to-feedback crashes on Jetpack Compose UIs

1 participant